{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Deep Learning Models -- A collection of various deep learning architectures, models, and tips for TensorFlow and PyTorch in Jupyter Notebooks.\n",
    "- Author: Sebastian Raschka\n",
    "- GitHub Repository: https://github.com/rasbt/deeplearning-models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sebastian Raschka \n",
      "\n",
      "CPython 3.7.3\n",
      "IPython 7.9.0\n",
      "\n",
      "torch 1.3.0\n"
     ]
    }
   ],
   "source": [
    "%load_ext watermark\n",
    "%watermark -a 'Sebastian Raschka' -v -p torch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Runs on CPU or GPU (if available)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Convolutional Autoencoder with Deconvolutions and Continuous Jaccard Distance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A convolutional autoencoder using deconvolutional layers that compresses 768-pixel MNIST images down to a 7x7x8 (392 pixel) representation.\n",
    "\n",
    "\n",
    "This convolutional VAE uses a continuous Jaccard distance. I.e., given 2 vectors, $x$ and $y$:\n",
    "\n",
    "$$J(x, y)=1-\\frac{\\sum_{i} \\min \\left(x_{i}, y_{i}\\right)}{\\sum_{i} \\max \\left(x_{i}, y_{i}\\right)}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Reference:\n",
    "    \n",
    "-    [1] https://en.wikipedia.org/wiki/Jaccard_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(0.6275)"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "\n",
    "\n",
    "def continuous_jaccard(x, y):\n",
    "    \"\"\"\n",
    "    Implementation of the continuous version of the\n",
    "    Jaccard distance:\n",
    "    1 - [sum_i min(x_i, y_i)] / [sum_i max(x_i, y_i)]\n",
    "    \"\"\"\n",
    "    c = torch.cat((x.view(-1).unsqueeze(1), y.view(-1).unsqueeze(1)), dim=1)\n",
    "\n",
    "    numerator = torch.sum(torch.min(c, dim=1)[0])\n",
    "    denominator = torch.sum(torch.max(c, dim=1)[0])\n",
    "\n",
    "    return 1. - numerator/denominator\n",
    "\n",
    "\n",
    "\n",
    "# Example\n",
    "\n",
    "x = torch.tensor([7, 2, 3, 4, 5, 6]).float()\n",
    "y = torch.tensor([1, 8, 9, 10, 11, 4]).float()\n",
    "\n",
    "continuous_jaccard(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Additional Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "if torch.cuda.is_available():\n",
    "    torch.backends.cudnn.deterministic = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "0it [00:00, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Device: cuda:0\n",
      "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to data/MNIST/raw/train-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "9920512it [00:02, 3410868.60it/s]                             \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/train-images-idx3-ubyte.gz to data/MNIST/raw\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "32768it [00:00, 280881.47it/s]                           \n",
      "0it [00:00, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to data/MNIST/raw/train-labels-idx1-ubyte.gz\n",
      "Extracting data/MNIST/raw/train-labels-idx1-ubyte.gz to data/MNIST/raw\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to data/MNIST/raw/t10k-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "1654784it [00:00, 1928783.37it/s]                            \n",
      "8192it [00:00, 113077.53it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/t10k-images-idx3-ubyte.gz to data/MNIST/raw\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to data/MNIST/raw/t10k-labels-idx1-ubyte.gz\n",
      "Extracting data/MNIST/raw/t10k-labels-idx1-ubyte.gz to data/MNIST/raw\n",
      "Processing...\n",
      "Done!\n",
      "Image batch dimensions: torch.Size([128, 1, 28, 28])\n",
      "Image label dimensions: torch.Size([128])\n"
     ]
    }
   ],
   "source": [
    "##########################\n",
    "### SETTINGS\n",
    "##########################\n",
    "\n",
    "# Device\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "print('Device:', device)\n",
    "\n",
    "# Hyperparameters\n",
    "random_seed = 456\n",
    "learning_rate = 0.005\n",
    "num_epochs = 10\n",
    "batch_size = 128\n",
    "\n",
    "\n",
    "##########################\n",
    "### MNIST DATASET\n",
    "##########################\n",
    "\n",
    "# Note transforms.ToTensor() scales input images\n",
    "# to 0-1 range\n",
    "train_dataset = datasets.MNIST(root='data', \n",
    "                               train=True, \n",
    "                               transform=transforms.ToTensor(),\n",
    "                               download=True)\n",
    "\n",
    "test_dataset = datasets.MNIST(root='data', \n",
    "                              train=False, \n",
    "                              transform=transforms.ToTensor())\n",
    "\n",
    "\n",
    "train_loader = DataLoader(dataset=train_dataset, \n",
    "                          batch_size=batch_size, \n",
    "                          shuffle=True)\n",
    "\n",
    "test_loader = DataLoader(dataset=test_dataset, \n",
    "                         batch_size=batch_size, \n",
    "                         shuffle=False)\n",
    "\n",
    "# Checking the dataset\n",
    "for images, labels in train_loader:  \n",
    "    print('Image batch dimensions:', images.shape)\n",
    "    print('Image label dimensions:', labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "##########################\n",
    "### MODEL\n",
    "##########################\n",
    "\n",
    "\n",
    "class ConvolutionalAutoencoder(torch.nn.Module):\n",
    "\n",
    "    def __init__(self):\n",
    "        super(ConvolutionalAutoencoder, self).__init__()\n",
    "        \n",
    "        # calculate same padding:\n",
    "        # (w - k + 2*p)/s + 1 = o\n",
    "        # => p = (s(o-1) - w + k)/2\n",
    "        \n",
    "        ### ENCODER\n",
    "        \n",
    "        # 28x28x1 => 28x28x4\n",
    "        self.conv_1 = torch.nn.Conv2d(in_channels=1,\n",
    "                                      out_channels=4,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(28-1) - 28 + 3) / 2 = 1\n",
    "                                      padding=1) \n",
    "        # 28x28x4 => 14x14x4                              \n",
    "        self.pool_1 = torch.nn.MaxPool2d(kernel_size=(2, 2),\n",
    "                                         stride=(2, 2),\n",
    "                                         # (2(14-1) - 28 + 2) / 2 = 0\n",
    "                                         padding=0)                                       \n",
    "        # 14x14x4 => 14x14x8\n",
    "        self.conv_2 = torch.nn.Conv2d(in_channels=4,\n",
    "                                      out_channels=8,\n",
    "                                      kernel_size=(3, 3),\n",
    "                                      stride=(1, 1),\n",
    "                                      # (1(14-1) - 14 + 3) / 2 = 1\n",
    "                                      padding=1)                 \n",
    "        # 14x14x8 => 7x7x8                             \n",
    "        self.pool_2 = torch.nn.MaxPool2d(kernel_size=(2, 2),\n",
    "                                         stride=(2, 2),\n",
    "                                         # (2(7-1) - 14 + 2) / 2 = 0\n",
    "                                         padding=0)\n",
    "        \n",
    "        ### DECODER\n",
    "                                         \n",
    "        # 7x7x8 => 15x15x4                          \n",
    "        self.deconv_1 = torch.nn.ConvTranspose2d(in_channels=8,\n",
    "                                                 out_channels=4,\n",
    "                                                 kernel_size=(3, 3),\n",
    "                                                 stride=(2, 2),\n",
    "                                                 padding=0)\n",
    "        \n",
    "        # 15x15x4  => 31x31x1                           \n",
    "        self.deconv_2 = torch.nn.ConvTranspose2d(in_channels=4,\n",
    "                                                 out_channels=1,\n",
    "                                                 kernel_size=(3, 3),\n",
    "                                                 stride=(2, 2),\n",
    "                                                 padding=0)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \n",
    "        ### ENCODER\n",
    "        x = self.conv_1(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = self.pool_1(x)\n",
    "        x = self.conv_2(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = self.pool_2(x)\n",
    "        \n",
    "        ### DECODER\n",
    "        x = self.deconv_1(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        x = self.deconv_2(x)\n",
    "        x = F.leaky_relu(x)\n",
    "        logits = x[:, :, 2:30, 2:30]\n",
    "        probas = torch.sigmoid(logits)\n",
    "        return logits, probas\n",
    "\n",
    "    \n",
    "torch.manual_seed(random_seed)\n",
    "model = ConvolutionalAutoencoder()\n",
    "model = model.to(device)\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 001/010 | Batch 000/468 | Cost: 0.8663\n",
      "Epoch: 001/010 | Batch 050/468 | Cost: 0.8086\n",
      "Epoch: 001/010 | Batch 100/468 | Cost: 0.7729\n",
      "Epoch: 001/010 | Batch 150/468 | Cost: 0.7322\n",
      "Epoch: 001/010 | Batch 200/468 | Cost: 0.3983\n",
      "Epoch: 001/010 | Batch 250/468 | Cost: 0.2963\n",
      "Epoch: 001/010 | Batch 300/468 | Cost: 0.2927\n",
      "Epoch: 001/010 | Batch 350/468 | Cost: 0.2783\n",
      "Epoch: 001/010 | Batch 400/468 | Cost: 0.2780\n",
      "Epoch: 001/010 | Batch 450/468 | Cost: 0.2609\n",
      "Time elapsed: 0.14 min\n",
      "Epoch: 002/010 | Batch 000/468 | Cost: 0.2694\n",
      "Epoch: 002/010 | Batch 050/468 | Cost: 0.2671\n",
      "Epoch: 002/010 | Batch 100/468 | Cost: 0.2444\n",
      "Epoch: 002/010 | Batch 150/468 | Cost: 0.2378\n",
      "Epoch: 002/010 | Batch 200/468 | Cost: 0.2540\n",
      "Epoch: 002/010 | Batch 250/468 | Cost: 0.2515\n",
      "Epoch: 002/010 | Batch 300/468 | Cost: 0.2393\n",
      "Epoch: 002/010 | Batch 350/468 | Cost: 0.2528\n",
      "Epoch: 002/010 | Batch 400/468 | Cost: 0.2283\n",
      "Epoch: 002/010 | Batch 450/468 | Cost: 0.2420\n",
      "Time elapsed: 0.27 min\n",
      "Epoch: 003/010 | Batch 000/468 | Cost: 0.2317\n",
      "Epoch: 003/010 | Batch 050/468 | Cost: 0.2274\n",
      "Epoch: 003/010 | Batch 100/468 | Cost: 0.2489\n",
      "Epoch: 003/010 | Batch 150/468 | Cost: 0.2246\n",
      "Epoch: 003/010 | Batch 200/468 | Cost: 0.2178\n",
      "Epoch: 003/010 | Batch 250/468 | Cost: 0.2200\n",
      "Epoch: 003/010 | Batch 300/468 | Cost: 0.2200\n",
      "Epoch: 003/010 | Batch 350/468 | Cost: 0.2309\n",
      "Epoch: 003/010 | Batch 400/468 | Cost: 0.2215\n",
      "Epoch: 003/010 | Batch 450/468 | Cost: 0.2218\n",
      "Time elapsed: 0.40 min\n",
      "Epoch: 004/010 | Batch 000/468 | Cost: 0.2124\n",
      "Epoch: 004/010 | Batch 050/468 | Cost: 0.2191\n",
      "Epoch: 004/010 | Batch 100/468 | Cost: 0.2121\n",
      "Epoch: 004/010 | Batch 150/468 | Cost: 0.2184\n",
      "Epoch: 004/010 | Batch 200/468 | Cost: 0.2118\n",
      "Epoch: 004/010 | Batch 250/468 | Cost: 0.2090\n",
      "Epoch: 004/010 | Batch 300/468 | Cost: 0.2114\n",
      "Epoch: 004/010 | Batch 350/468 | Cost: 0.2150\n",
      "Epoch: 004/010 | Batch 400/468 | Cost: 0.2218\n",
      "Epoch: 004/010 | Batch 450/468 | Cost: 0.2015\n",
      "Time elapsed: 0.53 min\n",
      "Epoch: 005/010 | Batch 000/468 | Cost: 0.1985\n",
      "Epoch: 005/010 | Batch 050/468 | Cost: 0.2053\n",
      "Epoch: 005/010 | Batch 100/468 | Cost: 0.2067\n",
      "Epoch: 005/010 | Batch 150/468 | Cost: 0.2003\n",
      "Epoch: 005/010 | Batch 200/468 | Cost: 0.2004\n",
      "Epoch: 005/010 | Batch 250/468 | Cost: 0.2076\n",
      "Epoch: 005/010 | Batch 300/468 | Cost: 0.2006\n",
      "Epoch: 005/010 | Batch 350/468 | Cost: 0.2162\n",
      "Epoch: 005/010 | Batch 400/468 | Cost: 0.2137\n",
      "Epoch: 005/010 | Batch 450/468 | Cost: 0.2077\n",
      "Time elapsed: 0.67 min\n",
      "Epoch: 006/010 | Batch 000/468 | Cost: 0.1986\n",
      "Epoch: 006/010 | Batch 050/468 | Cost: 0.2048\n",
      "Epoch: 006/010 | Batch 100/468 | Cost: 0.2063\n",
      "Epoch: 006/010 | Batch 150/468 | Cost: 0.2069\n",
      "Epoch: 006/010 | Batch 200/468 | Cost: 0.2092\n",
      "Epoch: 006/010 | Batch 250/468 | Cost: 0.1947\n",
      "Epoch: 006/010 | Batch 300/468 | Cost: 0.2006\n",
      "Epoch: 006/010 | Batch 350/468 | Cost: 0.1927\n",
      "Epoch: 006/010 | Batch 400/468 | Cost: 0.2018\n",
      "Epoch: 006/010 | Batch 450/468 | Cost: 0.1964\n",
      "Time elapsed: 0.79 min\n",
      "Epoch: 007/010 | Batch 000/468 | Cost: 0.1809\n",
      "Epoch: 007/010 | Batch 050/468 | Cost: 0.1996\n",
      "Epoch: 007/010 | Batch 100/468 | Cost: 0.1942\n",
      "Epoch: 007/010 | Batch 150/468 | Cost: 0.1909\n",
      "Epoch: 007/010 | Batch 200/468 | Cost: 0.1894\n",
      "Epoch: 007/010 | Batch 250/468 | Cost: 0.1937\n",
      "Epoch: 007/010 | Batch 300/468 | Cost: 0.1956\n",
      "Epoch: 007/010 | Batch 350/468 | Cost: 0.1938\n",
      "Epoch: 007/010 | Batch 400/468 | Cost: 0.1963\n",
      "Epoch: 007/010 | Batch 450/468 | Cost: 0.2060\n",
      "Time elapsed: 0.92 min\n",
      "Epoch: 008/010 | Batch 000/468 | Cost: 0.1947\n",
      "Epoch: 008/010 | Batch 050/468 | Cost: 0.2044\n",
      "Epoch: 008/010 | Batch 100/468 | Cost: 0.1811\n",
      "Epoch: 008/010 | Batch 150/468 | Cost: 0.1980\n",
      "Epoch: 008/010 | Batch 200/468 | Cost: 0.1794\n",
      "Epoch: 008/010 | Batch 250/468 | Cost: 0.2008\n",
      "Epoch: 008/010 | Batch 300/468 | Cost: 0.1949\n",
      "Epoch: 008/010 | Batch 350/468 | Cost: 0.1843\n",
      "Epoch: 008/010 | Batch 400/468 | Cost: 0.1942\n",
      "Epoch: 008/010 | Batch 450/468 | Cost: 0.1932\n",
      "Time elapsed: 1.05 min\n",
      "Epoch: 009/010 | Batch 000/468 | Cost: 0.1901\n",
      "Epoch: 009/010 | Batch 050/468 | Cost: 0.1894\n",
      "Epoch: 009/010 | Batch 100/468 | Cost: 0.1976\n",
      "Epoch: 009/010 | Batch 150/468 | Cost: 0.1935\n",
      "Epoch: 009/010 | Batch 200/468 | Cost: 0.1949\n",
      "Epoch: 009/010 | Batch 250/468 | Cost: 0.1921\n",
      "Epoch: 009/010 | Batch 300/468 | Cost: 0.1917\n",
      "Epoch: 009/010 | Batch 350/468 | Cost: 0.1900\n",
      "Epoch: 009/010 | Batch 400/468 | Cost: 0.1913\n",
      "Epoch: 009/010 | Batch 450/468 | Cost: 0.1815\n",
      "Time elapsed: 1.19 min\n",
      "Epoch: 010/010 | Batch 000/468 | Cost: 0.1845\n",
      "Epoch: 010/010 | Batch 050/468 | Cost: 0.1910\n",
      "Epoch: 010/010 | Batch 100/468 | Cost: 0.1929\n",
      "Epoch: 010/010 | Batch 150/468 | Cost: 0.1919\n",
      "Epoch: 010/010 | Batch 200/468 | Cost: 0.1822\n",
      "Epoch: 010/010 | Batch 250/468 | Cost: 0.1974\n",
      "Epoch: 010/010 | Batch 300/468 | Cost: 0.1919\n",
      "Epoch: 010/010 | Batch 350/468 | Cost: 0.1750\n",
      "Epoch: 010/010 | Batch 400/468 | Cost: 0.1879\n",
      "Epoch: 010/010 | Batch 450/468 | Cost: 0.1785\n",
      "Time elapsed: 1.32 min\n",
      "Total Training Time: 1.32 min\n"
     ]
    }
   ],
   "source": [
    "start_time = time.time()\n",
    "for epoch in range(num_epochs):\n",
    "    for batch_idx, (features, targets) in enumerate(train_loader):\n",
    "        \n",
    "        # don't need labels, only the images (features)\n",
    "        features = features.to(device)\n",
    "\n",
    "        ### FORWARD AND BACK PROP\n",
    "        logits, decoded = model(features)\n",
    "        #cost = F.binary_cross_entropy_with_logits(logits, features)\n",
    "        cost = continuous_jaccard(features, decoded)\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        cost.backward()\n",
    "        \n",
    "        ### UPDATE MODEL PARAMETERS\n",
    "        optimizer.step()\n",
    "        \n",
    "        ### LOGGING\n",
    "        if not batch_idx % 50:\n",
    "            print ('Epoch: %03d/%03d | Batch %03d/%03d | Cost: %.4f' \n",
    "                   %(epoch+1, num_epochs, batch_idx, \n",
    "                     len(train_dataset)//batch_size, cost))\n",
    "            \n",
    "    print('Time elapsed: %.2f min' % ((time.time() - start_time)/60))\n",
    "    \n",
    "print('Total Training Time: %.2f min' % ((time.time() - start_time)/60))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABH0AAACqCAYAAAAwYjMwAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+17YcXAAAgAElEQVR4nO3dd7wU1fnH8c/EEntXRBS72FBUFLtRwW5UNHbFRMWW2I38NNbEXmMXu0Yx9q6RYOwVRKwIdlEs2Hud3x/wnZndu7t39m6d3e/79fJ1l9nZ3XMfZ2bPnfOc5wRhGGJmZmZmZmZmZq3lN41ugJmZmZmZmZmZVZ9v+piZmZmZmZmZtSDf9DEzMzMzMzMza0G+6WNmZmZmZmZm1oJ808fMzMzMzMzMrAX5po+ZmZmZmZmZWQuq6KZPEAQbBUHwahAErwVBMKRajTIzMzMzMzMzs8oEYRh27YVBMBUwDhgATACeAXYIw/Dl6jXPzMzMzMzMzMy6opJMn1WA18IwfCMMwx+B64EtqtMsMzMzMzMzMzOrxNQVvLYH8G7i3xOAfqVeMNdcc4ULLbRQBR+ZTaNGjZoUhuHc5bzGsUrPsSpPO8brrbfeYtKkSUG5r2vHWIHPw3I4Vuk5Vun5+p6er+/l8XmYnmOVnq9Z6fmaVR6fh+mVilUlN30KHawd5ooFQTAYGAzQs2dPRo4cWcFHZlMQBG+n3M+xcqxSSxurKfu2dbz69u2bet92jxX4PCyHY5WeY5Wer+/p+fpeHp+H6TlW6fmalZ6vWeXxeZheqVhVMr1rArBA4t/zA+/n7xSG4dAwDPuGYdh37rnLvgHcVhyr9Byr8jhe6TlW6TlW6TlW6TlW5XG80nOs0nOs0nOsyuN4pedYpedYlVbJTZ9ngMWDIFg4CIJpge2BO6rTLDMzMzMzMzMzq0SXp3eFYfhzEAR/Bv4DTAVcHobhS1VrmZmZmZmZmZmZdVklNX0Iw/Ae4J4qtcXMzMzMzMzMzKqkkuldZmZmZmZmZmbWpCrK9MmiddddF4AHH3ww2nbMMccA8Lvf/S7npxW37bbbAjB27FgAzj33XADWWWedhrWpUu+/P7kO+cknnwzAFVdcAcDXX39d9DXzzjsvAEceeSQAO+ywQ/TcnHPOWZN2mpmZWXaMGzcOgAsuuACAc845B4AzzzwTgAMPPLAxDTOzVF577TUABg4cCMCLL74YPae/I/XTrBk508fMzMzMzMzMrAW1XaZPMsNHjjvuOAAeeuihnO3O+Ono+++/B+Ddd98F4jvdJ5xwApDtTJ9nn30WgPPOOw+AAQMGALDJJptE+yy11FIA/PDDDwCcdtppAOy///4AjB49Otr3sssuq3GLG+vHH38EYMSIEQBcd911ANx0003RPjpe8q2xxhoAbLPNNtG2PffcE4AZZ5yx+o21zPrqq68AWHHFFaNtK6+8MgBXX301AFNPXfyr7NtvvwVg5513BmDSpEkA3HPP5HJ0M800U5VbbGaWS991L700eb2TIAga2ZyW87///Q+Agw8+GIAFF1wQgNtuu61hbbLW8NFHHwGw/vrrA/HfP8lz2OezZYEzfczMzMzMzMzMWlDbZfqEYQjEtX0gzv7J/6mRA2f8xP773/8C8NRTTwEw3XTTAbDllls2rE3V8t577wGw2WabAXDLLbcApbMIll9+eQD69esHxJkHAFtssQUAv//976vf2DpTdsTRRx8dbbv++usB+Pzzz4H43FIWBsBUU00FwAcffADA22+/DcBjjz0GwOOPPx7tqxG5I444AoANNtigyr+FZdGECRNyfkJ8PH366acAzDPPPEVff/nllwPx8bXqqqsC8Nvf/rb6jW0SvXv3jh4PGjQIgEMPPbRRzTHrVDILOz8jO2v1Ft966y0gt8bf+PHjc/bR9ad79+51a1er+OWXX6LHhx12GBBnVn/55ZcA/PWvf61/w5rIww8/DORm3993330AbLjhhiVfA3D//fcDcX9shhlmqEk7m436sxD/vTN48GAAvvjii6Kv099CrUR1TocOHQrA8ccfD8Daa68NwJgxY6J9i8VGf0dneRZIK3Gmj5mZmZmZmZlZC2q7TB/R3UeIR5VU2yf/31kZXaqHO+64I+ffu+++OwD77rtvI5pTVXvttVfOzzR69uwJwG677QbAKaecEj13+umnA62R6aMsKGVNQLxy2XbbbQfAIYccAsDCCy8c7aNMH42eKGNILr744uixVoDbfvvtgXjedLvU+Pn444+BeCW4m2++GYBpppkGgL/97W/Rvn/+85/r3LrGUaZdMuPum2++AYrXjEpS/S3p1asX0Jpz8LWaYp8+faJtGvFWHa3VVlut/g2ztnfsscfm/Fv9qzS0r7JJm9Un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jRX42CpHu8eq2c7DZuVYpedYpefre3myGqtG9C18HqbnWKXXrtesX3/9NfqvnPO4HWPVVT4P00sTq0pu+jwDLB4EwcJBEEwLbA/cUcH7mZmZmZmZmZlZlXR5elcYhj8HQfBn4D/AVMDlYRi+VLWW1ciXX34JwMSJE6NtSolacsklAfjNbypayb4tKDPqp59+AuI71+XewW5G7733HgAffvghAD/++GP03EwzzQTAxx9/DMBKK62Us93HjpmZmRWiPqj6CjPMMEPOv82suf3888/A5Lo8suCCCwIwzTTTNKJJZqlUUtOHMAzvAe6pUlvMzMzMzMzMzKxKPLRgZmZmZmZmZtaCKsr0yRJNQ5p11lmL7jNmzBgAlltuubq0KYs0Fe6yyy4DYK+99gLgzjvvBGCTTTaJ9s1auvIXX3wBwPzzz1/2a0877TQAll9++WjbOuusA8C0005bhdY1P0350zEC8Xn37bffAnHq63TTTQfA1FPHlyAVwWuWgtnWXN58883o8fTTTw/APPPMA6S71uj4/PXXX4HWmIpqZtmg78D8Pujo0aOB3L6DvwO7rp4r9Fp7UVkH9TuSRo0aBcCKK65Y1zaZlSNbf5WbmZmZmZmZmVkqbZPpk8w+KEYjLW+88QYACy20EOARg6R3330XiDN8ZPPNNwdyix5nLdPn/fffL7j9xBNPjB6vv/76AIwYMQKAI444AoDDDjusw+uefPJJAPr161fVdjaSsqEAnn32WSAuaqfzZu+99079fscdd1z0eK211gLi4tizzDJLZY21lqBssUUWWaTDc5999hkAs802W9HX6/i89957c95vq622Alrz+p78vtN1uBV/T2ttWc/a0HciwKKLLlpwn1dffRXIzfSx9HSte/755wEYO3YsABtuuCEAs88+e7RvVo+jrtD3XKFsaivP22+/DcR/Exby9NNPA62d6aMsaR1bSepn5B93+pm1vwdblf8vmJmZmZmZmZm1oLbJ9Pntb38LxHdjV1lllaL7Xn311QBsu+22ACy++OLRc8m75u1Ioyn5hg0bBsQjc1mku/gPP/wwEB8jOnaSlI2iDIN99923wz6tNKry0UcfAdCtW7eqvu8xxxzTYduZZ54JwJ577gnATDPNVNXPbHYaKfn666+BuCZUsjaURk3aoS6NsnkKKXaOJa9Dd911FxBn9sgPP/wAtFbNLWVaDh06NNo211xzAbDFFlsAcT0ks3rK7xsoQ0O1bpLLH48bNw6Ia7/pu3m++eaL9lEmaDP1yfQ76vuyWHZPUp8+fYDW6i/UWvJYuvTSS4GOGcZPPPEEACuvvHK0rR2+L7/66isAhg8fDkDv3r2j57SseLHvvPHjx0ePldWhY7iZzrNa+v7776PHt912GwA77LBDp69bY401atamekqeW5988gkAr732GhD/bXT44Yd3+j5///vfAVhmmWUAWG211aLn9HeEr3n150wfMzMzMzMzM7MW1B63bonvKOqu/4cffhg9d+211wJw8MEHA3Dsscfm/JwwYUK0b48ePWrd1KaTrA+h0eJ8a6+9NpDtUXONgKu2TCkaMdpuu+2Awpk+qiHSt29fINtzWpO1mop56aWXAFhiiSWibaqnomNIo0d6v2uuuSba96CDDgLi81ArwfXq1auitmfFe++9B8AJJ5wAwIUXXpjz/A033BA9Xm+99QCYc84569S6xtFoUyG6jmvUX9f55PGan+EjOjazfM3Kp6yov/zlLx2e0/dYO36HWW3puq7zT/9++eWXo32eeuopIM7g7Ir77rsvejxgwIAuv0+tqObdRRddlPo1peqEWGHJbIxiNQSVod8O2T0QfxfOO++8OdvPOuus6LHqHCk26pOq71Zo5WLFutUzfb777jsAZphhhtSvufXWW6PHWe+nqs/0+OOPR9vWXXfdLr/fUUcdVfS5SZMmAa3Zf83/W0eZ+8msJl2TGtH3zO5foWZmZmZmZmZmVlRr37otYZ555oke77fffgAccsghQMe55xqhAhg4cGAdWtdcNCJeyJ133gnkzrVvJ3PMMQcQj6LPP//80XPKFNMKX1nO9Jl77rkBuOKKK6JtCy+8MBCvqrTAAgt0eF1no0M77rhj9FiZPqIV0hZbbDGgtUbsdPf/lFNOibaVGhmBuMYYxPUKdPy18txorRhYSLFVGUtdsyTL9ceKSY485lONqHanc0///5NZA19++SUQj0Tqmj3NNNMAuRlkuh7p+FRm4qmnngrA1ltvHe2ra2UWz1PFST9Vgwfi7MRbbrkFiL/rakX1qaC5vk8VG9UA0Xd/Kddddx1QuGaglfb5558XfU590lbMIijklVdeAWDppZcu+LyuaQDffPMNEJ+3+p4slOEjrZrho3NWq+cttdRSnb5GNY9UGynZ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\n",
      "text/plain": [
       "<Figure size 1440x180 with 30 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "##########################\n",
    "### VISUALIZATION\n",
    "##########################\n",
    "\n",
    "n_images = 15\n",
    "image_width = 28\n",
    "\n",
    "fig, axes = plt.subplots(nrows=2, ncols=n_images, \n",
    "                         sharex=True, sharey=True, figsize=(20, 2.5))\n",
    "orig_images = features[:n_images]\n",
    "decoded_images = decoded[:n_images]\n",
    "\n",
    "for i in range(n_images):\n",
    "    for ax, img in zip(axes, [orig_images, decoded_images]):\n",
    "        curr_img = img[i].detach().to(torch.device('cpu'))\n",
    "        ax[i].imshow(curr_img.view((image_width, image_width)), cmap='binary')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch       1.3.0\n",
      "matplotlib  3.1.0\n",
      "torchvision 0.4.1a0+d94043a\n",
      "numpy       1.17.2\n",
      "\n"
     ]
    }
   ],
   "source": [
    "%watermark -iv"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
